﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Cloak.Globalization;
using Cloak.Reflection;

namespace Ruleweaver.Rules
{
	public abstract class Rule
	{
		#region Check

		public static CheckRule Check(MethodInfo method, IEnumerable<object> checkArguments)
		{
			Contract.Requires(method != null);
			Contract.Requires(checkArguments != null);
			Contract.Ensures(Contract.Result<CheckRule>() != null);

			return new CheckRule(method, checkArguments);
		}

		public static CheckRule Check(MethodInfo method, params object[] checkArguments)
		{
			Contract.Requires(method != null);
			Contract.Requires(checkArguments != null);
			Contract.Ensures(Contract.Result<CheckRule>() != null);

			return Check(method, checkArguments as IEnumerable<object>);
		}
		#endregion

		#region Fixed

		public static FixedRule Fixed(bool passes)
		{
			return new FixedRule(passes);
		}
		#endregion

		#region Lambda

		public static LambdaRule Lambda(LambdaExpression lambda)
		{
			Contract.Requires(lambda != null);

			return new LambdaRule(lambda);
		}
		#endregion

		#region Operators

		public static CompositeRule Composite(RuleType ruleType, IEnumerable<Rule> rules)
		{
			Contract.Requires(rules != null);
			Contract.Requires(ruleType == RuleType.And || ruleType == RuleType.Or || ruleType == RuleType.ExclusiveOr);
			Contract.Ensures(Contract.Result<CompositeRule>() != null);

			return new CompositeRule(ruleType, rules);
		}

		public static CompositeRule Composite(RuleType ruleType, params Rule[] rules)
		{
			Contract.Requires(rules != null);
			Contract.Requires(ruleType == RuleType.And || ruleType == RuleType.Or || ruleType == RuleType.ExclusiveOr);
			Contract.Ensures(Contract.Result<CompositeRule>() != null);

			return Composite(ruleType, rules as IEnumerable<Rule>);
		}

		public static CompositeRule And(IEnumerable<Rule> rules)
		{
			Contract.Requires(rules != null);
			Contract.Ensures(Contract.Result<CompositeRule>() != null);
			
			return new CompositeRule(RuleType.And, rules);
		}

		public static CompositeRule And(params Rule[] rules)
		{
			Contract.Requires(rules != null);
			Contract.Ensures(Contract.Result<CompositeRule>() != null);

			return And(rules as IEnumerable<Rule>);
		}

		public static CompositeRule Or(IEnumerable<Rule> rules)
		{
			Contract.Requires(rules != null);
			Contract.Ensures(Contract.Result<CompositeRule>() != null);

			return new CompositeRule(RuleType.Or, rules);
		}

		public static CompositeRule Or(params Rule[] rules)
		{
			Contract.Requires(rules != null);
			Contract.Ensures(Contract.Result<CompositeRule>() != null);

			return Or(rules as IEnumerable<Rule>);
		}

		public static CompositeRule ExclusiveOr(IEnumerable<Rule> rules)
		{
			Contract.Requires(rules != null);
			Contract.Ensures(Contract.Result<CompositeRule>() != null);

			return new CompositeRule(RuleType.ExclusiveOr, rules);
		}

		public static CompositeRule ExclusiveOr(params Rule[] rules)
		{
			Contract.Requires(rules != null);
			Contract.Ensures(Contract.Result<CompositeRule>() != null);

			return ExclusiveOr(rules as IEnumerable<Rule>);
		}

		public static NotRule Not(Rule rule)
		{
			Contract.Requires(rule != null);
			Contract.Ensures(Contract.Result<NotRule>() != null);

			return new NotRule(rule);
		}
		#endregion

		#region Members

		public static MemberRule Member(MemberInfo member, Rule rule)
		{
			Contract.Requires(member != null);
			Contract.Requires(member is FieldInfo || member is PropertyInfo || member is MethodInfo);
			Contract.Requires(rule != null);
			Contract.Ensures(Contract.Result<MemberRule>() != null);

			var field = member as FieldInfo;

			if(field != null)
			{
				return Field(field, rule);
			}

			var property = member as PropertyInfo;

			if(property != null)
			{
				return Property(property, rule);
			}

			return Method((MethodInfo) member, rule);
		}

		public static MemberRule Field(FieldInfo field, Rule rule)
		{
			Contract.Requires(field != null);
			Contract.Requires(rule != null);
			Contract.Ensures(Contract.Result<MemberRule>() != null);

			return new MemberRule(RuleType.Field, field, field.FieldType, rule);
		}

		public static MemberRule Property(PropertyInfo property, Rule rule)
		{
			Contract.Requires(property != null);
			Contract.Requires(rule != null);
			Contract.Ensures(Contract.Result<MemberRule>() != null);

			return new MemberRule(RuleType.Property, property, property.PropertyType, rule);
		}

		public static MemberRule Method(MethodInfo method, Rule rule)
		{
			Contract.Requires(method != null);
			Contract.Requires(rule != null);
			Contract.Ensures(Contract.Result<MemberRule>() != null);

			return new MemberRule(RuleType.Method, method, method.ReturnType, rule);
		}
		#endregion

		protected Rule(RuleType type)
		{
			Type = type;
		}

		public RuleType Type { get; private set; }

		public override string ToString()
		{
			return new ConvertRuleToString().ConvertToString(this);
		}

		public LambdaExpression ToLambdaExpression(Type targetType)
		{
			Contract.Requires(targetType != null);
			
			return new ConvertRuleToLambdaExpression().ConvertToLambdaExpression(this, targetType);
		}

		public Expression<Func<TTarget, bool>> ToLambdaExpression<TTarget>()
		{
			var lambda = ToLambdaExpression(typeof(TTarget));

			return Expression.Lambda<Func<TTarget, bool>>(lambda.Body, lambda.Parameters);
		}

		public Func<TTarget, bool> ToFunction<TTarget>()
		{
			return ToLambdaExpression<TTarget>().Compile();
		}

		public Func<object, bool> ToFunction(Type targetType)
		{
			Contract.Requires(targetType != null);

			return ToObjectLambda(targetType).Compile();
		}

		public Rule MergeWith(Rule otherRule)
		{
			Contract.Requires(otherRule != null);

			return new RuleMerger().MergeRules(this, otherRule);
		}

		private Expression<Func<object, bool>> ToObjectLambda(Type targetType)
		{
			var lambda = ToLambdaExpression(targetType);

			// Building: untypedTarget => lambda(CastTarget<TTarget>(untypedTarget))

			var untypedTargetParameter = Expression.Parameter(typeof(object), "untypedTarget");

			var castTargetCall = Expression.Call(
				typeof(Rule),
				"CastTarget",
				new[] { targetType },
				untypedTargetParameter);

			var invokeLambda = Expression.Invoke(lambda, castTargetCall);

			return Expression.Lambda<Func<object, bool>>(invokeLambda, untypedTargetParameter);
		}

		private static T CastTarget<T>(object target)
		{
			var isValueType = !typeof(T).IsAssignableNull();

			if(isValueType && target == null)
			{
				throw new InvalidCastException(Resources.TargetTypeCannotBeAssignedNull.FormatInvariant(typeof(T)));
			}

			if((isValueType || target != null) && !(target is T))
			{
				throw new InvalidCastException(Resources.InvalidTargetCast.FormatInvariant(target.GetType(), typeof(T)));
			}

			return (T) target;
		}
	}
}